package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"reflect"
	"strconv"

	// "strconv"
	"strings"
	"time"

	"gitee.com/oscstudio/gate/internal/client"
	"gitee.com/oscstudio/gate/internal/gitalypb"
	recursive "github.com/nanorobocop/go-recursive"
	"github.com/ouqiang/goproxy"
	jsonrpc "github.com/teambition/jsonrpc-go"
)

type EventHandler struct{}
type stubMapping map[string]interface{}
type RpcHandler struct {
	grpc_pool *client.Pool
}

var RpcFunc RpcHandler

// ===================================== Graphql 代理层 =========================================//
func (e *EventHandler) Connect(ctx *goproxy.Context, rw http.ResponseWriter) {
	// 保存的数据可以在后面的回调方法中获取
	ctx.Data["req_id"] = "uuid"

	// 禁止访问外部域名
	// if !strings.Contains(ctx.Req.URL.Host, "gitee.com") {
	//	rw.WriteHeader(http.StatusForbidden)
	//	ctx.Abort()
	//	return
	// }
}

func (e *EventHandler) Auth(ctx *goproxy.Context, rw http.ResponseWriter) {
	// 身份验证
}
func (e *EventHandler) BeforeRequest(ctx *goproxy.Context) {
	// 修改header
	ctx.Req.Header.Add("X-Request-Id", ctx.Data["req_id"].(string))
	// 设置X-Forwarded-For
	if clientIP, _, err := net.SplitHostPort(ctx.Req.RemoteAddr); err == nil {
		if prior, ok := ctx.Req.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		ctx.Req.Header.Set("X-Forwarded-For", clientIP)
	}
	// 读取Body
	body, err := ioutil.ReadAll(ctx.Req.Body)
	bodyString := string(body)
	fmt.Println(bodyString)

	if err != nil {
		// 错误处理
		return
	}

	var payload interface{}
	err = json.Unmarshal(body, &payload) // Convert JSON data into interface{} type
	if err == nil {
		m := payload.(map[string]interface{}) // To

		// m["hookBy"] = "Golang"
		if m["operationName"] == "IntrospectionQuery" {
			ctx.Req.Header.Add("X-Operation", "IntrospectionQuery")
		}

		b, err := json.Marshal(m)
		if err != nil {
			return
		}
		// 修改response
		// resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
		ctx.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
	} else {
		ctx.Req.Body = ioutil.NopCloser(bytes.NewReader(body))
	}

}

func (e *EventHandler) BeforeResponse(ctx *goproxy.Context, resp *http.Response, err error) {
	if err != nil {
		return
	}

	// 读取Body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// 错误处理
		return
	}
	// bodyString := string(body)
	// fmt.Println(bodyString)
	var payload interface{}
	err = json.Unmarshal(body, &payload) // Convert JSON data into interface{} type
	if err == nil {
		m := payload.(map[string]interface{}) // To

		// m["hookBy"] = "Golang"
		// if _, ok := ctx.Req.Header["X-Operation"]; ok {
		recursive.Go(&m, findAndInvokeRpcFunc)
		// }

		b, err := json.Marshal(m)
		if err != nil {
			return
		}
		// bodyString := string(b)
		// fmt.Println(bodyString)
		// 修改response
		// resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
		resp.Body = ioutil.NopCloser(bytes.NewReader(b))
	} else {
		resp.Body = ioutil.NopCloser(bytes.NewReader(body))
	}

}

// 设置上级代理
func (e *EventHandler) ParentProxy(req *http.Request) (*url.URL, error) {
	return url.Parse("http://localhost:3000")
}

func (e *EventHandler) Finish(ctx *goproxy.Context) {
	fmt.Printf("请求结束 URL:%s\n", ctx.Req.URL)
}

// 记录错误日志
func (e *EventHandler) ErrorLog(err error) {
	log.Println(err)
}

// =========================================  RPC 实现层 =======================================================
func (r RpcHandler) AddNumbers(arg0 int, arg1 int) int {
	return arg0 + arg1
}
func (r RpcHandler) GetCommitCount(storage, diskPath, glProject string) int {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5000)*time.Second)
	defer cancel()
	conn, err := RpcFunc.grpc_pool.Dial(ctx, "tcp://127.0.0.1:9999", "")
	if err != nil {
		fmt.Printf("failed to connect: %s", err)
		return 0
	}
	repo := &gitalypb.Repository{
		StorageName:   storage,
		RelativePath:  diskPath + ".git",
		GlProjectPath: glProject,
	}
	commitServiceClient := gitalypb.NewCommitServiceClient(conn)
	req := &gitalypb.CountCommitsRequest{
		Repository: repo,
		Revision:   []byte("HEAD"),
	}
	countResp, err := commitServiceClient.CountCommits(ctx, req)
	if err != nil {
		fmt.Printf("failed to get commit count: %s", err)
		return 0
	}
	return int(countResp.GetCount())
}
func (r RpcHandler) GetRepoPackagistUrl(storage, diskPath, glProject string) string {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5000)*time.Second)
	defer cancel()
	conn, err := RpcFunc.grpc_pool.Dial(ctx, "tcp://127.0.0.1:9999", "")
	if err != nil {
		fmt.Printf("failed to connect: %s", err)
		return ""
	}
	repo := &gitalypb.Repository{
		StorageName:   storage,
		RelativePath:  diskPath + ".git",
		GlProjectPath: glProject,
	}
	commitServiceClient := gitalypb.NewCommitServiceClient(conn)
	req := &gitalypb.TreeEntryRequest{
		Repository: repo,
		Revision:   []byte("HEAD"),
		Path:       []byte("composer.json"),
	}
	treeEntryClient, err := commitServiceClient.TreeEntry(ctx, req)
	if err != nil {
		fmt.Printf("failed to load commit: %s", err)
		return ""
	}
	resp, err := treeEntryClient.Recv()
	if err != nil {
		fmt.Printf("failed to get raw: %s", err)
		return ""
	}
	var buf bytes.Buffer
	buf.Write(resp.GetData())
	var composer interface{}
	err = json.Unmarshal(buf.Bytes(), &composer)
	if err != nil {
		fmt.Printf("failed to extract composer name: %s", err)
		return ""
	}
	m := composer.(map[string]interface{}) // To
	return fmt.Sprintf("https://packagist.org/packages/%s?hookBy=Go", m["name"])
}
func findAndInvokeRpcFunc(obj interface{}, level int) interface{} {
	if text, ok := obj.(string); ok {
		fmt.Println(level, text)
		rpc, _ := jsonrpc.ParseString(text)
		fmt.Println("method:", rpc.Method)
		fmt.Println("parmas:", rpc.Params)
		switch rpc.Method {
		case "AddNumbers":
			args := rpc.Params.([]interface{})
			res := Call(RpcFunc, rpc.Method, int(args[0].(float64)), int(args[1].(float64)))
			fmt.Println(res)
			return strconv.Itoa(int(res[0].Int()))
		case "GetRepoPackagistUrl":
			args := rpc.Params.([]interface{})
			res := Call(RpcFunc, rpc.Method, args[0], args[1], args[2])
			return res[0].String()
		case "GetCommitCount":
			args := rpc.Params.([]interface{})
			res := Call(RpcFunc, rpc.Method, args[0], args[1], args[2])
			return res[0].Int()
		}
		return text
	}

	return obj // return unchanged
}
func main() {
	proxy := goproxy.New(goproxy.WithDelegate(&EventHandler{}))
	RpcFunc = RpcHandler{
		grpc_pool: client.NewPool(),
	}
	server := &http.Server{
		Addr:         ":8080",
		Handler:      proxy,
		ReadTimeout:  1 * time.Minute,
		WriteTimeout: 1 * time.Minute,
	}
	err := server.ListenAndServe()
	if err != nil {
		panic(err)
	}
}

func Call(obj interface{}, fn string, args ...interface{}) (res []reflect.Value) {
	method := reflect.ValueOf(obj).MethodByName(fn)
	var inputs []reflect.Value
	for _, v := range args {
		inputs = append(inputs, reflect.ValueOf(v))
	}
	fmt.Println("method:", method)
	fmt.Println("parmas:", inputs)
	return method.Call(inputs)
}
